package liquidfs.core;

import java.sql.*;
import java.util.logging.*;
import java.util.*;
import java.util.Date;

import utility.*;

import liquidfs.*;
import liquidfs.data.*;
import liquidfs.data.fs.*;

public class MetadatabaseInterface {
   
   private String framework = "embedded";
   private String driver = "org.apache.derby.jdbc.EmbeddedDriver";
   private String protocol = "jdbc:derby:";
   
   private Connection connection;
   private String databaseName;
   
   private Application app;
   private Logger log = Logger.getLogger("liquidfs.core.Metadatabase");
   
   private PreparedStatement getMetadataStatement;
   private PreparedStatement setMetadataStatement;
   private PreparedStatement addMetadataStatement;
   private PreparedStatement delMetadataStatement;
   private PreparedStatement getFieldStatement;
   private PreparedStatement setFieldStatement;
   private PreparedStatement addFieldStatement;
   
   private Context context;
   
   private void initialize () {
      
      try {
         // Create the database
         connection = DriverManager.getConnection(protocol + databaseName + ";create=true");
         
         // Create the metadata table
         Statement statement = connection.createStatement();
         statement.execute("CREATE TABLE LiquidMetadata (Path VARCHAR(1024), ModifierID VARCHAR(25), ModificationCount BIGINT, CreationDate TIMESTAMP, ModificationDate TIMESTAMP, LastAccessedDate TIMESTAMP)");
        
         // Create the LiquidGlobals table
         statement = connection.createStatement();
         statement.execute("CREATE TABLE LiquidGlobals (Field VARCHAR(25), Value VARCHAR(100))");
      }
      catch (SQLException e) {
         throw new UserUncheckedException("Unable to create new database: " + e.getMessage(), e);
      }
   }
   
   public MetadatabaseInterface (Application app) {
      
      this.app = app;
      System.setProperty("derby.system.home", app.getConfiguration().getBackendPath());
      this.databaseName = "LiquidMetadataDatabase";
      
      try {
         connection = DriverManager.getConnection(protocol + databaseName);
         
         if (connection == null) {
            throw new UserUncheckedException("Another instance of the application is already connected to the Metadatabase");
         }
      }
      catch (SQLException e) {
         if (e.getMessage().contains("not found")) {
            initialize ();
         }
         else {
            throw new UserUncheckedException("Another instance of the application is already connected to the Metadatabase.\nDetails: " + e.getLocalizedMessage());
         }
      }
      
      try {
         // LiquidMetadata statements
         getMetadataStatement = connection.prepareStatement("SELECT * FROM LiquidMetadata WHERE Path=?");
         setMetadataStatement = connection.prepareStatement("UPDATE LiquidMetadata SET ModifierID=?, ModificationCount=?, CreationDate=?, ModificationDate=?, LastAccessedDate=? WHERE Path=?");
         addMetadataStatement = connection.prepareStatement("INSERT INTO LiquidMetadata (Path, ModifierID, ModificationCount, CreationDate, ModificationDate, LastAccessedDate) VALUES (?, ?, ?, ?, ?, ?)");
         
         // Field statements
         getFieldStatement = connection.prepareStatement("SELECT Value FROM LiquidGlobals WHERE Field=?");
         setFieldStatement = connection.prepareStatement("UPDATE LiquidGlobals SET Value=? WHERE Field=?");
         addFieldStatement = connection.prepareStatement("INSERT INTO LiquidGlobals (Field, Value) VALUES (?, ?)");
      }
      catch (SQLException e) {
         throw new UserUncheckedException ("Unable to initialize database: " + e.getMessage(), e);
      }
   }
   
   public String getField (String field) {
      try {
         getFieldStatement.setString(1, field);
         ResultSet rs = getFieldStatement.executeQuery();
         rs.next();
         return rs.getString("Value");
      }
      catch (SQLException e) {
         return null;
      }
   }
   
   public void setField (String field, String value) {
      try {
         setFieldStatement.setString(1, value);
         setFieldStatement.setString(2, field);
         setFieldStatement.executeUpdate();
      }
      catch (SQLException e) {
         throw new IllegalArgumentException("Could not set the global field \"" + field + "\" to value \"" + value + "\"");
      }
   }
   
   public void addField (String field, String value) {
      try {
         addFieldStatement.setString(1, field);
         addFieldStatement.setString(2, value);
         addFieldStatement.executeUpdate();
      }
      catch (SQLException e) {
         throw new IllegalArgumentException("Could not add the global field \"" + field + "\" with value \"" + value + "\"");
      }
   }
   
   public Metadata getMetadata (String path) {
      
      try {
         getMetadataStatement.setString(1, path);
         ResultSet rs = getMetadataStatement.executeQuery();
         rs.next();
         Revision revision = new Revision(app.getConfiguration().getPeerDevice(rs.getString("ModifierID")), rs.getLong("ModificationCount"));
         Metadata meta = new Metadata(revision, rs.getTimestamp("CreationDate"), rs.getTimestamp("ModificationDate"), rs.getTimestamp("LastAccessedDate"));
         return meta;
      }
      catch (SQLException e) {
         //throw new UserUncheckedException("Unable to find metadata for path " + path, e);
         Metadata meta = new Metadata(new Revision(app.getConfiguration().getLocalDevice(), 0), new Date(), new Date(), new Date());
         this.addMetadata(path, meta);
         return meta;
      }
      
   }
   
   public void setMetadata (String path, Metadata meta) {
      try {
         int field = 1;
         setMetadataStatement.setString(field++, meta.getRevision().getDevice().getID());
         setMetadataStatement.setLong(field++, meta.getRevision().getRevisionNumber());
         setMetadataStatement.setTimestamp(field++, new Timestamp(meta.getCreationDate().getTime()));
         setMetadataStatement.setTimestamp(field++, new Timestamp(meta.getModificationDate().getTime()));
         setMetadataStatement.setTimestamp(field++, new Timestamp(meta.getLastAccessedDate().getTime()));
         setMetadataStatement.setString(field++, path);
         
         setMetadataStatement.executeUpdate();
      }
      catch (SQLException e) {
         throw new IllegalArgumentException("Could not set the metadata for path " + path + ": " + e.getMessage(), e);
      }
   }
   
   public void addMetadata (String path, Metadata meta) {
      try {
         
         int field = 1;
         addMetadataStatement.setString(field++, path);
         addMetadataStatement.setString(field++, meta.getRevision().getDevice().getID());
         addMetadataStatement.setLong(field++, meta.getRevision().getRevisionNumber());
         addMetadataStatement.setTimestamp(field++, new Timestamp(meta.getCreationDate().getTime()));
         addMetadataStatement.setTimestamp(field++, new Timestamp(meta.getModificationDate().getTime()));
         addMetadataStatement.setTimestamp(field++, new Timestamp(meta.getLastAccessedDate().getTime()));
         
         addMetadataStatement.execute();
      }
      catch (SQLException e) {
         throw new UserUncheckedException("Failed to add metadata for path " + path + ": " + e.getMessage(), e);
      }
   }
}
